Skip to content

Normalize out-of-range integer literal casts - #180

Open
nikswamy wants to merge 1 commit into
mainfrom
nswamy/pal-fold-integer-literal-casts
Open

Normalize out-of-range integer literal casts#180
nikswamy wants to merge 1 commit into
mainfrom
nswamy/pal-fold-integer-literal-casts

Conversation

@nikswamy

Copy link
Copy Markdown
Contributor

Generated C interfaces commonly define fixed-width bit-pattern constants as explicit casts from long integer literals and expand them into switch tables. On LP64 hosts, a high-bit long literal is a signed 64-bit value, while casting it to int32_t denotes a negative target bit pattern under PAL's two's-complement model. Emitting the source literal followed by Int.Cast.int64_to_int32 leaves F* with an out-of-range conversion and prevents otherwise routine generated switches from verifying.

Clang exposes integer literal bit patterns through a signed BigInt representation, even when the C literal type is unsigned. Recover the source literal mathematical value from its signedness and width before deciding whether a cast is representable in the target type.

Preserve representable casts as explicit F* Int.Cast operations: these conversions have defined C semantics and retaining the conversion term is useful to downstream proofs. For out-of-range casts, normalize to PAL's target representation and emit a target-typed literal directly.

Use one machine-literal emitter for ordinary literals and folded casts. Emit native F* suffixes for every supported C width: y/uy, s/us, l/ul, and L/UL, with parenthesized negative signed literals and constructor fallbacks for non-standard widths.

Add a dedicated regression covering direct and X-macro-generated switch tables of high-bit signed 32-bit codes, all eight suffix forms, narrowing and wraparound at every width, UINT64_MAX converted to signed int64, and a representable int64-to-uint64 cast that remains explicit.

Generated C interfaces commonly define fixed-width bit-pattern constants as explicit casts from long integer literals and expand them into switch tables. On LP64 hosts, a high-bit long literal is a signed 64-bit value, while casting it to int32_t denotes a negative target bit pattern under PAL's two's-complement model. Emitting the source literal followed by Int.Cast.int64_to_int32 leaves F* with an out-of-range conversion and prevents otherwise routine generated switches from verifying.

Clang exposes integer literal bit patterns through a signed BigInt representation, even when the C literal type is unsigned. Recover the source literal mathematical value from its signedness and width before deciding whether a cast is representable in the target type.

Preserve representable casts as explicit F* Int.Cast operations: these conversions have defined C semantics and retaining the conversion term is useful to downstream proofs. For out-of-range casts, normalize to PAL's target representation and emit a target-typed literal directly.

Use one machine-literal emitter for ordinary literals and folded casts. Emit native F* suffixes for every supported C width: y/uy, s/us, l/ul, and L/UL, with parenthesized negative signed literals and constructor fallbacks for non-standard widths.

Add a dedicated regression covering direct and X-macro-generated switch tables of high-bit signed 32-bit codes, all eight suffix forms, narrowing and wraparound at every width, UINT64_MAX converted to signed int64, and a representable int64-to-uint64 cast that remains explicit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 981f7d3c-6a91-47ad-84d7-7aadf747123d
Comment thread src/pass/emit.rs
if !integer_fits(&literal_value, *signed, *width) {
return emit_machine_int_literal(&literal_value, *signed, *width);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably go in a separate cast normalization pass probably,

Comment thread src/pass/emit.rs
} => normalize_unsigned(val, width),
_ => val.as_ref().clone(),
};
if !integer_fits(&literal_value, *signed, *width) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit surprising to gate this on fitting: this simplification is always valid, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading up on this, the simplification is in general only valid if it fits (but not in the special case implemented by this PR).

Unsigned-to-signed casts are implementation-defined when the value doesn't fit. I'm happy to accept the restriction to two's-complement though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants